//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "aura.h"
#include "pppdrv.h"
//#include <mutex.h>

PppTran* g_pPppDrv;

typedef PVoid sio_fd_t;

extern "C" int pppSetAddr(int pd, UInt32 our,  UInt32 host,  UInt32 mask,  UInt32 dns1,
                UInt32 dns2);
extern "C" int pppGetAddr(int pd, UInt32 *our,  UInt32 *host,  UInt32 *mask,
                UInt32 *dns1, UInt32 *dns2);
extern "C" int pppGetDNS(UInt32 *dns1, UInt32 *dns2);// get dns from default interface
extern "C" int  pppClose(int pd);
extern "C" int  pppWaitUp(int pd);
extern "C" int  pppIOCtl(int pd, int cmd, void *arg);
extern "C" int  pppOpen(sio_fd_t fd,
                void (*linkStatusCB)(void *ctx, int errCode, void *arg),
                void *linkStatusCtx);
extern "C" void pppSetAuth(enum pppAuthType authType, const char *user,
                const char *passwd);

extern "C" void pppInit(void *arg);
extern "C" void pppStopCB(void *arg);

extern "C" void pppInProc(int pd, char *s, int l);
extern void lwipentry();

PppTran::PppTran()
{
}

PppTran::~PppTran()
{
    for(int i = 0; i < OUT_BUF_NUM; i++) {
        if (m_pOutData[i].buf) free(m_pOutData[i].buf);
    }

    if (m_sDataCtrl.mt) {
        delete m_sDataCtrl.cond;
        delete m_sDataCtrl.mt;
    }
}

ECode PppTran::Init()
{
    int i;
    for(i = 0; i < OUT_BUF_NUM; i++) {
        m_pOutData[i].buf = (char*)malloc(PPP_BUF_SIZE);
        if (!m_pOutData[i].buf) return E_OUT_OF_MEMORY;
        m_pOutData[i].bUsed = FALSE;
        m_pOutData[i].nextIndex = (i + 1) % OUT_BUF_NUM;
    }

    m_sDataCtrl.mt = new DzMutex();
    if (!m_sDataCtrl.mt)   return E_OUT_OF_MEMORY;
    m_sDataCtrl.cond = new DzCondition();
    if (!m_sDataCtrl.cond)   return E_OUT_OF_MEMORY;

    m_sDataCtrl.dataAll = 0;
    for (i = 0; i < NUM_PPP; i++) {
        m_sDataCtrl.dataNum[i] = 0;
    }
    m_iWIndex = 0;

    pppInit(NULL);

    return NOERROR;
    //m_event = new DzEvent(TRUE, UNSIGNALED);
}

ECode PppTran::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 uNumberOfBytesToRead,
    /* [out] */ MemoryBuf * pData,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (u64Offset < 0 || u64Offset >= NUM_PPP
         || pData == NULL || pData->GetCapacity() > PPP_BUF_SIZE) {
        return E_INVALID_ARGUMENT;
    }

    Int32 iIndex, iPre, iTmp;
    WaitResult wr;

    m_sDataCtrl.mt->Lock(&wr);
    if (0 == m_sDataCtrl.dataAll) {
        m_sDataCtrl.cond->Wait(m_sDataCtrl.mt, &wr);
    }

    if (0 == m_sDataCtrl.dataAll || (m_sDataCtrl.dataNum[u64Offset] == 0)) {
        pData->SetUsed(0);
        m_sDataCtrl.mt->Unlock();
        return NOERROR;
    }

    iPre = iIndex = m_iWIndex;
    do {
        if (m_pOutData[iIndex].bUsed && m_pOutData[iIndex].pd == u64Offset) {
            pData->Copy((PByte)m_pOutData[iIndex].buf, m_pOutData[iIndex].len);
            m_pOutData[iIndex].bUsed = FALSE; /* this can't be moved below of 'if'*/
            if (m_pOutData[m_iWIndex].bUsed) {
                if (m_pOutData[iIndex].nextIndex == m_iWIndex) {
                    m_iWIndex = iIndex;
                }
                else {
                    /* tmp point to the prev of m_iWIndex */
                    iTmp = m_iWIndex;
                    while(m_pOutData[iTmp].nextIndex != m_iWIndex)
                        iTmp = m_pOutData[iTmp].nextIndex;
                    /* insert iIndex before m_iWIndex, and make m_iWIndex = m_iWIndex'prev */
                    m_pOutData[iPre].nextIndex = m_pOutData[iIndex].nextIndex;
                    m_pOutData[iTmp].nextIndex = iIndex;
                    m_pOutData[iIndex].nextIndex = m_iWIndex;
                    m_iWIndex = iIndex;
                }
            }
            m_sDataCtrl.dataAll--;
            m_sDataCtrl.dataNum[u64Offset]--;
            break;
        }
        iPre = iIndex;
        iIndex = m_pOutData[iIndex].nextIndex;
    } while(iIndex != m_iWIndex);

    m_sDataCtrl.mt->Unlock();
    return NOERROR;
}

ECode PppTran::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & ebbData,
    /* [out] */ Int32 * puNumberOfBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    // u64Offset is used for identity for ppp session
    if (u64Offset < 0 || u64Offset >= NUM_PPP || ebbData.IsNull()) {
        return E_INVALID_ARGUMENT;
    }

//printf("PppTran::Write----------------\n");
    pppInProc((int)u64Offset, (char*)ebbData.GetPayload(), ebbData.GetUsed());
    *puNumberOfBytesWritten = ebbData.GetUsed();

    return NOERROR;
}

ECode PppTran::Control(
    /* [in] */ Int32 nControlCode,
    /* [in] */ const MemoryBuf & ebbInData,
    /* [out] */ MemoryBuf * pOutData,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    int pd = 0;
    switch (nControlCode) {
        case PPP_INIT:
            break;

        case PPP_SETAUTH:{
            PPPAuthData *authData = (PPPAuthData *)ebbInData.GetPayload();

            pppSetAuth(authData->authType, authData->user, authData->passwd);
        }
        break;

        case PPP_OPEN:{
            pd = pppOpen((sio_fd_t)0, NULL, NULL);
            if (pd >= 0) {
                pOutData->Copy((PByte)&pd, 4);
            }
            else {
                pOutData->SetUsed(0);
            }
        }
        break;

        case PPP_WAITUP:{
            pd = *(int *)ebbInData.GetPayload();

            if (pd >= 0 && pd < NUM_PPP) {
                int i = pppWaitUp(pd);
                if (i < 0) {
                    return E_ABORT;
                }
            }
        }
        break;

        case PPP_CLOSE:{
            pd = *(int *)ebbInData.GetPayload();
            if (pd >= 0 && pd < NUM_PPP) {
                pppClose(pd);
            }
        }
        break;

        case PPP_SETADDR:{
            pd = *(int *)ebbInData.GetPayload();

            PPPAddr pppAddr;
            if (pd >=0 && pd < NUM_PPP) {
                memcpy(&pppAddr, (char *)ebbInData.GetPayload() + 4,
                            sizeof(pppAddr));

                pppSetAddr(pd, pppAddr.our,  pppAddr.host,
                       pppAddr.mask,  pppAddr.dns1, pppAddr.dns2);
            }
        }
        break;

        case PPP_GETADDR:{
            pd = *(int *)ebbInData.GetPayload();
            PPPAddr pppAddr;

            if (pd >= 0 && pd < NUM_PPP) {
                pppGetAddr(pd, &pppAddr.our, &pppAddr.host,
                       &pppAddr.mask, &pppAddr.dns1, &pppAddr.dns2);

                pOutData->Copy((PByte)&pppAddr, sizeof(pppAddr));
            }
        }
        break;

        case PPP_GETDNS: {
            UInt32 dns1 = 0, dns2 = 0;
            pppGetDNS(&dns1, &dns2);
            if (dns1) {
                pOutData->Copy((PByte)&dns1, 4);
                if (dns2) {
                    pOutData->Append((PByte)&dns2, 4);
                }
            }
        }
        break;

        case PPP_IOCTOL:{
            PPPIoctlData *ioctlData = (PPPIoctlData *)ebbInData.GetPayload();
            int ret = pppIOCtl(ioctlData->pd, ioctlData->cmd,
                            &ioctlData->arg);
            if (ret != 0) {
                pOutData->SetUsed(0);
            }
            else {
                pOutData->Copy((PByte)&ioctlData->pd, 4);
                pOutData->Append((PByte)&ioctlData->cmd, 4);
                pOutData->Append((PByte)&ioctlData->arg, 4);
                pOutData->SetUsed(12);
            }
        }
        break;

        case PPP_READABORT:{
            WaitResult wr;
            m_sDataCtrl.mt->Lock(&wr);
            m_sDataCtrl.cond->Pulse();
            m_sDataCtrl.mt->Unlock();
        }
        break;

        default:
            break;
    }

    return NOERROR;
}

void PppTran::Dispose()
{
}

int PppTran::PppWrite(Int32 fd, struct pbuf *nb)
{
    Int32 datalen = 0;
    struct pbuf *b;

    WaitResult wr;
    for (int i = 0; i < 500; i++ ) {
        m_sDataCtrl.mt->Lock(&wr);
        if (!m_pOutData[m_iWIndex].bUsed) {
            m_pOutData[m_iWIndex].len = 0;
            for (b = nb; b != NULL; b = b->next) {
                if (datalen + b->len > PPP_BUF_SIZE) {
                    datalen = 0;
                    break;
                }
                memcpy(m_pOutData[m_iWIndex].buf + datalen, b->payload, b->len);
                datalen += b->len;
            }

            if (datalen > 0) {
                m_pOutData[m_iWIndex].len = datalen;
                m_pOutData[m_iWIndex].bUsed = TRUE;
                m_pOutData[m_iWIndex].pd = fd;
                m_sDataCtrl.dataNum[fd]++;
                m_sDataCtrl.dataAll++;
                m_iWIndex = m_pOutData[m_iWIndex].nextIndex;
            }

            m_sDataCtrl.cond->Pulse();
            m_sDataCtrl.mt->Unlock();

            return (datalen - 1);
        }
        else {
            m_sDataCtrl.mt->Unlock();
        }

        DzSleep(1, NULL);
    }

    return 0;
}

extern "C" int PppWrite(Int32 fd, struct pbuf *nb)
{
    if (!g_pPppDrv) return -1;
    if (fd < 0 || fd >=NUM_PPP ) {
        kprintf("pppdrv.cpp PppWrite fd = %d\n", fd);
        return -1;
    }
    return g_pPppDrv->PppWrite(fd, nb);
}

EXTERN IDeviceDriver * CDECL CreatePPPDriver(uint_t uDeviceNo, void *pvParameter)
{
    PppTran *pPppdrv = new PppTran();
    if (NULL == pPppdrv) {
        kprintf("out of memory\n");
        return NULL;
    }

    ECode ec = pPppdrv->Init();
    if (FAILED(ec)) {
        kprintf("PPP init fail.\n");
        delete pPppdrv;
        return NULL;
    }

    g_pPppDrv = pPppdrv;
    lwipentry();
    pPppdrv->AddRef();
    return pPppdrv;
}
